package com.example.mq.mqchile;

import com.example.mq.common.*;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: DongGuoZhen
 * @Date: 2024/03/08/14:42
 * @Description:
 * 1. 持有Socket对象
 * 2. 写入请求/读取响应
 * 3. 管理多个channel对象
 */
public class Connection {
    private Socket socket = null;

    //管理多个channel 使用hash表,把若干个channel组织起来
    private ConcurrentHashMap<String,Channel> channelMap = new ConcurrentHashMap<>();

    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    //执行回调方法的线程池
    private ExecutorService callbackPool = null;

    public Connection(String host,int port) throws IOException {
        socket = new Socket(host,port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);
        callbackPool  = Executors.newFixedThreadPool(4);
        //创建一个扫描线程,由这个线程负责不停的从socket中读取响应的数据,把这个响应数据再交给对应的channel负责处理
        Thread t = new Thread(()->{
            try{
                while (!socket.isClosed()) {
                    Response response = readResponse();
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                //连接正常关闭,可以时间忽略
                System.out.println("[Connection] 连接正常断开!");
            } catch(IOException | ClassNotFoundException | MqException e) {
                System.out.println("[Connection] 连接异常断开!");
                e.printStackTrace();
            }
        });
        t.start();
    }


    //关闭connection释放资源
    public void close() {
        try{
            callbackPool.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        }catch (IOException e) {

        }
    }

    //使用这个方法来分别处理当前的响应是针对处理请求的响应, 还是服务器推送的消息
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if(response.getType() == 0xc) {
            //服务器推送的消息
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            // 根据channelId找到对应的channel对象
            //执行该channel对象内部的回调
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if(channel == null) {
                throw new MqException("[Connection] 该消息对应的channel在客户端中不存在! channelId = " + channel.getChannelId());
            }
            //调用channel中的回调 把这个回调具体要做什么的任务交给线程池去执行
            callbackPool.submit(()->{
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(),
                            subScribeReturns.getBasicProperties(), subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            //当前响应是针对刚才的控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            //把这个结果方到对应channel的hash表中
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if(channel == null) {
                throw new MqException("[Connection] 该消息对应的channel在客户端中不存在! channelId = " + channel.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }

    //发送请求
    public void  writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        System.out.println("[Connection] 发送请求! type = " + request.getType() + ",length = " + request.getLength());
    }
    //读取响应
    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if(n != response.getLength()) {
            throw new IOException("读取响应数据不完整!");
        }
        response.setPayload(payload);
        System.out.println("[Connection] 收到响应! type = " + response.getType() + ",length = " + response.getLength());
        return response;
    }


    //通过这个方法,在Connection中创建一个channel
    //一个Connection中可以包含多个channel
    public Channel createChannel() throws IOException {
        String channelId = "C-" + UUID.randomUUID().toString();
        Channel channel = new Channel(channelId,this);
        //把这个channel对象放在 Connection 管理的 channel 的hash表中   channelMap
        channelMap.put(channelId,channel);
        //同时也需要把创建channel的消息告诉服务器
        boolean ok = channel.createChannel();
        if(!ok) {
            //服务器这里创建失败,整个这个创建channel操作不顺利 删除刚才已经加入hash表的键值对
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }
}
